#include "GraphicsApp.h"

#include "NeroGame\Nero.h"
#include "Win32\Win32.h"
#include "DirectX\DirectX11.h"

#include "Ball.h"
#include "TriangleApp.h"
#include "CubeApp.h"
#include "ShadowApp.h"
#include "CameraController.h"

using namespace Engine;
using namespace Graphics;
using namespace Game;
using namespace System;

/*** Scene To Load ************************************************************/
std::string GScene = "..\\..\\assets\\xml\\3D_Game_World.xml";

/*** Game Instance ************************************************************/
GraphicsApp GraphicsApp::s_Instance;

GraphicsApp::GraphicsApp(void)
	:NGame(),
	 mModelList(NULL),
	 mFrustum(NULL),
	 mParticleSystem(NULL),
	 mParticleShader(NULL),
	 mCurrentState(PARTICLE_STATE)
{
}

GraphicsApp::~GraphicsApp(void) 
{
}

void GraphicsApp::Initialize(const std::string& scene)
{
	if(!mIsInitialized)
	{
		NGame::Initialize(scene);

		mModelList = MY_NEW(Memory::HID_Rendering,"Model List") Graphics::ModelList();
		mModelList->Initialize(100);

		mLightShader = MY_NEW(Memory::HID_Rendering, "Light Shader") Graphics::LightShader();
		mLightShader->Initialize();

		mLight = MY_NEW(Memory::HID_Rendering,"Light") Graphics::Light();
		mLight->Initialize();

		mFrustum = MY_NEW(Memory::HID_Rendering,"Frustum") Graphics::Frustum();

		mSphereModel = MY_NEW(Memory::HID_Rendering, "Spere") Ball();
		mSphereModel->Initialize();

		Graphics::Camera* camera = GetPlatform()->GetCamera()->As<Graphics::Camera>();
		if(camera)
		{
			camera->SetPosition(NVector3(0.0f, -2.0f, -10.0f));
			camera->AddComponent(MY_NEW(Memory::HID_Game,"Cam Controller") CameraController());
		}

		mParticleShader = MY_NEW(Memory::HID_Rendering, "ParticleShader") Graphics::ParticleShader();
		mParticleShader->Initialize();

		mParticleSystem = MY_NEW(Memory::HID_Rendering, "ParticleSystem") Graphics::ParticleSystem();
		mParticleSystem->Initialize();
	
		mTextureShader = MY_NEW(Memory::HID_Rendering, "Texture Shader") Graphics::TextureShader();
		mTextureShader->Initialize();

		mTriApp = MY_NEW(Memory::HID_Game, "TriApp") TriangleApp();
		mTriApp->SetPlatform(GetPlatform());
		mTriApp->Initialize(scene);

		mCubeApp = MY_NEW(Memory::HID_Game, "CubeApp") CubeApp();
		mCubeApp->SetPlatform(GetPlatform());
		mCubeApp->Initialize(scene);

		mColorShader = MY_NEW(Memory::HID_Rendering, "Color Shader") Graphics::ColorShader();
		mColorShader->Initialize();

		/*mShadowApp = MY_NEW(Memory::HID_Game, "ShadowApp") ShadowApp();
		mShadowApp->SetPlatform(GetPlatform());
		mShadowApp->Initialize(scene);*/

		mIsInitialized = true;
	}
}

void GraphicsApp::Deinitialize()
{
	MY_DELETE(mModelList);
	MY_DELETE(mFrustum);
	MY_DELETE(mLight);
	MY_DELETE(mLightShader);
	MY_DELETE(mSphereModel);
	MY_DELETE(mParticleShader);
	MY_DELETE(mParticleSystem);
	MY_DELETE(mTriApp);
	MY_DELETE(mCubeApp);
	MY_DELETE(mTextureShader);
	MY_DELETE(mColorShader);
	MY_DELETE(mShadowApp);

	NGame::Deinitialize();
}

void GraphicsApp::Update(float gameTime)
{
	NGame::Update(gameTime);

	// Run the frame processing for the particle system.
	if(mParticleSystem)
	{
		mParticleSystem->Update(gameTime);
	}
	
	if(mTriApp)
	{
		mTriApp->Update(gameTime);
	}
	if(mCubeApp)
	{
		mCubeApp->Update(gameTime);
	}
	if(mShadowApp)
	{
		mShadowApp->Update(gameTime);
	}

	HandleInput();
}

void GraphicsApp::Render()
{
	static float rotation = 0.0f;
	if(mIsInitialized)
	{
		D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
		Graphics::D3DDevice* device = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();
#ifdef WIN32	
		Win32Graphics* graphics = (Win32Graphics*)GetPlatform()->GetGraphics();
#endif
		worldMatrix = device->GetWorldMatrix();
		projectionMatrix = device->GetProjectionMatrix();
		viewMatrix = Utility::NeroToDXD(GetPlatform()->GetCamera()->GetViewMatrix());

		int modelCount = 0;
		int renderCount = 0;
		D3DXVECTOR4 color;

		switch(mCurrentState)
		{
		case TRIAPP_STATE:
			mTriApp->Render();
			break;

		case CUBE_STATE:
			mCubeApp->Render();
			break;
		
		case SHADOW_STATE:
			mShadowApp->Render();
			break;

		case PARTICLE_STATE:
			device->TurnOnAlphaBlending();		
			mParticleSystem->Render();
			mParticleShader->Render(mParticleSystem->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, mParticleSystem->GetTexture());
			device->TurnOffAlphaBlending();
			
		#ifdef WIN32
			graphics->GetDebugDisplay()->SetDebugText(100, 1150, 20, "Number of Particles: %i", mParticleSystem->GetCurrentNumberOfParticles());
		#endif
			break;

		case FRUSTUM_STATE:
			// Update the rotation variable each frame.
			rotation += (float)D3DX_PI * 0.01f;
			if(rotation > 360.0f)
			{
				rotation -= 360.0f;
			}
			D3DXMatrixRotationY(&viewMatrix, rotation);

			mFrustum->ConstructFrustum(SCREEN_DEPTH, projectionMatrix, viewMatrix);

			// Get the number of models that will be rendered.
			modelCount = mModelList->GetModelCount();
			float positionX, positionY, positionZ, radius;
			bool renderModel;
		
			// Go through all the models and render them only if they can be seen by the camera view.
			for(int index=0; index<modelCount; index++)
			{
				// Get the position and color of the sphere model at this index.
				mModelList->GetData(index, positionX, positionY, positionZ, color);

				// Set the radius of the sphere to 1.0 since this is already known.
				radius = 1.0f;
		
				// Check if the sphere model is in the view frustum.
				renderModel = mFrustum->CheckSphere(positionX, positionY, positionZ, radius);

				// If it can be seen then render it, if not skip this model and check the next sphere.
				if(renderModel)
				{
					// Move the model to the location it should be rendered at.
					D3DXMatrixTranslation(&worldMatrix, positionX, positionY, positionZ); 

					// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
					mSphereModel->Render();

					// Render the model using the light shader.
					mLightShader->Render(mSphereModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
										 GetPlatform()->GetCamera()->As<Graphics::Camera>(), mLight, 
										 Graphics::TextureResourceManager::GetInstance()->GetResource("SeaFloor"));

					// Reset to the original world matrix.
					worldMatrix = device->GetWorldMatrix();

					// Since this model was rendered then increase the count for this frame.
					renderCount++;
				}
			}

	#ifdef WIN32
			graphics->GetDebugDisplay()->SetDebugText(101, 1150, 40, "Render Count: %i", renderCount);
	#endif
			break;
		default:
			break;
		}
	}
}

void GraphicsApp::PreRender()
{
	switch (mCurrentState)
	{
	case CUBE_STATE:
		if(mCubeApp)
		{
			mCubeApp->PreRender();
		}
		break;
	case SHADOW_STATE:
		if(mShadowApp)
		{
			mShadowApp->PreRender();
		}
		break;
	default:
		break;
	}
}

void GraphicsApp::HandleInput()
{
	if(GetInput())
	{
		if(GetInput()->IsKeyDown(KEY::Key_1))
		{
			mCurrentState = PARTICLE_STATE;
		}
		if(GetInput()->IsKeyDown(KEY::Key_2))
		{
			mCurrentState = TRIAPP_STATE;
		}
		if(GetInput()->IsKeyDown(KEY::Key_3))
		{
			mCurrentState = CUBE_STATE;
		}
		if(GetInput()->IsKeyDown(KEY::Key_4))
		{
			mCurrentState = FRUSTUM_STATE;
		}
		if(GetInput()->IsKeyDown(KEY::Key_5))
		{
			mCurrentState = SHADOW_STATE;
		}
	}
}